import {
  AxesHelper,
  Clock,
  Material,
  Mesh,
  PerspectiveCamera,
  Scene,
  WebGLRenderer
} from 'three'
import { OrbitControls } from 'three/examples/jsm/Addons.js'
import GUI from 'three/examples/jsm/libs/lil-gui.module.min.js'
import { onMounted, onUnmounted, ref, shallowRef, type Ref } from 'vue'

interface UseThreeOptions {
  antialias?: boolean
  alpha?: boolean
}

type AnimationCallback = (delta: number, elapsedTime: number) => void

export const useThree = (
  container: Ref<HTMLCanvasElement | null>,
  options: UseThreeOptions = {}
) => {
  const scene = shallowRef<Scene | null>(null)
  const camera = shallowRef<PerspectiveCamera | null>(null)
  const renderer = shallowRef<WebGLRenderer | null>(null)
  const controls = shallowRef<OrbitControls | null>(null)
  const animationId = ref<number | null>(null)
  const animationCallbacks = ref<AnimationCallback[]>([])
  const clock = new Clock()
  const gui = new GUI()

  const init = () => {
    if (!container.value) {
      console.error('Container is not defined')
      return
    }
    const { clientWidth, clientHeight } = container.value

    // Scene
    scene.value = new Scene()

    // AxesHelper
    const axesHelper = new AxesHelper(5)
    scene.value.add(axesHelper)

    // Camera
    camera.value = new PerspectiveCamera(
      75,
      clientWidth / clientHeight,
      0.1,
      1000
    )
    camera.value.position.z = 5
    scene.value.add(camera.value)

    // Controls
    controls.value = new OrbitControls(camera.value, container.value)

    // Renderer
    renderer.value = new WebGLRenderer({
      canvas: container.value,
      antialias: options.antialias ?? true,
      alpha: options.alpha ?? false
    })
    renderer.value.setSize(clientWidth, clientHeight)
    renderer.value.setPixelRatio(Math.min(window.devicePixelRatio, 2))

    clock.start()
  }

  const animate = () => {
    animationId.value = requestAnimationFrame(animate)

    const delta = clock.getDelta()
    const elapsedTime = clock.getElapsedTime()

    animationCallbacks.value.forEach((callback) => {
      callback(delta, elapsedTime)
    })

    if (scene.value && camera.value && renderer.value) {
      controls.value?.update()
      renderer.value.render(scene.value, camera.value)
    }
  }

  const addAnimationCallback = (callback: AnimationCallback) => {
    animationCallbacks.value.push(callback)
  }

  const removeAnimationCallback = (callback: AnimationCallback) => {
    const index = animationCallbacks.value.indexOf(callback)
    if (index > -1) {
      animationCallbacks.value.splice(index, 1)
    }
  }

  const handleResize = () => {
    if (!container.value || !renderer.value || !camera.value) return
    const { clientWidth, clientHeight } = container.value

    camera.value.aspect = clientWidth / clientHeight
    camera.value.updateProjectionMatrix()
    renderer.value.setSize(clientWidth, clientHeight)
    renderer.value.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  }

  const dispose = () => {
    if (animationId.value) {
      cancelAnimationFrame(animationId.value)
    }
    clock.stop()

    // 销毁 GUI
    gui.destroy()

    if (renderer.value && container.value) {
      renderer.value.dispose()
    }

    scene.value?.traverse((object) => {
      if (object instanceof Mesh) {
        object.geometry.dispose()

        if (object.material instanceof Material) {
          object.material.dispose()
        } else if (Array.isArray(object.material)) {
          object.material.forEach((material) => material.dispose())
        }
      }
    })
  }

  onMounted(() => {
    init()
    animate()
    window.addEventListener('resize', handleResize)
  })

  onUnmounted(() => {
    window.removeEventListener('resize', handleResize)
    dispose()
  })

  return {
    scene,
    camera,
    renderer,
    gui,
    addAnimationCallback,
    removeAnimationCallback
  }
}
